package com.phynos.wechat.wx;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;

/**
 * 微信API的鉴权和状态保持管理类
 * <p>
 *    微信相关的js-API需要进行权限验证，该验证包含2层，2层都有有效时间
 *    1.accessToken
 *    2.ticket
 * 
 * </p>
 * <p>
 * URL签名的时候，需要先判断accessToken和ticket的时效性
 * </p>
 * @author lupc
 *
 */
public class WeChatLogin {

	private static final Logger LOG = Logger.getLogger(WeChatLogin.class);

	private static final GsonBuilder GSON_BUILDER = new GsonBuilder();
	
	//----------------------------------------
	
	private String timestamp;

	private String nonceStr;
	
	//URL签名是动态的，请调用doSignature进行签名
	//private String signature;

	//----------------------------------------
	
	private String appId;
	
	private String appSecret;

	/**
	 * 如果要使用动态菜单，在微信服务器配置的预留token值
	 */
	public String wechatPreToken;
	
	//----------------------------------------
	
	private String accessToken;	
	private int tokenExpiresIn;
	private Date tokenTime;

	private String ticket;
	private int ticketExpiresIn;	
	private Date ticketTime;
	
	//----------------------------------------
	
	public WeChatLogin(String appId,String appSecret){
		this.appId = appId;
		this.appSecret = appSecret;
		
		tokenTime = new Date();
		tokenExpiresIn = 0;

		ticketTime = new Date();
		ticketExpiresIn = 0;		
	}

	private void checkTokenOrRefresh(){
		if(checkTokenExpireIn()){
			//1、获取AccessToken  
			JsonToken token = getAccessToken(appId,appSecret);				
			//
			setTokenExpiresIn(token.accessToken,token.expiresIn);	
		}		
	}
	
	private void checkTicketOrRefresh(){
		if(checkTicketExpireIn()){			
			//2、获取Ticket  
			JsonTicket jt = getTicket(accessToken);
			//3、时间戳和随机字符串  
			nonceStr = UUID.randomUUID().toString().replace("-", "").substring(0, 16);//随机字符串  
			timestamp = String.valueOf(System.currentTimeMillis() / 1000);//时间戳
			//
			ticket = jt.ticket;
			setTicketExpiresIn(jt.expiresIn);
		}
	}

	/**
	 * 对URL进行签名
	 * @param request
	 */
	public String doSignature(HttpServletRequest request){
		//签名之前应该检查 token和ticket时效性
		checkTokenOrRefresh();
		checkTicketOrRefresh();
		
		String uri = request.getRequestURI() 
				+ (request.getQueryString() != null ? "?" + request.getQueryString() : "");
		//5、将参数排序并拼接字符串  
		String url = request.getRemoteHost() + uri;
		LOG.debug("url:" + url);
		String str = "jsapi_ticket="+ ticket + "&noncestr="+ nonceStr+"&timestamp="+ timestamp + "&url="+url;  
		//6、将字符串进行sha1加密  
		String signature = WeChatUtil.SHA1(str); 
		return signature;
	}

	/**
	 * 获取accessToken
	 * @return {"access_token":"xxxx","expires_in":7200}
	 * @throws Exception
	 */
	private synchronized static JsonToken getAccessToken(String appId,String appSecret) {
		JsonToken result = null;
		try {
			String json = WeChatAPIProxy.getAccessToken(appId, appSecret);
			LOG.debug(json);
			
			Gson gson = GSON_BUILDER.disableHtmlEscaping()
					.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
					.setPrettyPrinting()
					.serializeNulls()
					.setDateFormat("yyyy-MM-dd HH:mm:ss")
					.create();
			result = gson.fromJson(json, JsonToken.class);
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
		}				
		return result;
	}

	private synchronized static JsonTicket getTicket(String accessToken) {  
		JsonTicket result = null; 		  
		try {
			String json = WeChatAPIProxy.getTicket(accessToken);
			LOG.debug(json);
			
			Gson gson = GSON_BUILDER.disableHtmlEscaping()
					.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
					.setPrettyPrinting()
					.serializeNulls()
					.setDateFormat("yyyy-MM-dd HH:mm:ss")
					.create();
			result = gson.fromJson(json, JsonTicket.class);
		} catch (Exception e) {  
			LOG.error(e.getMessage(), e);  
		}  
		return result; 		
	}	

	private void setTokenExpiresIn(String token,int expiresIn){
		this.accessToken = token;
		this.tokenExpiresIn = expiresIn;
		tokenTime = new Date();
	}

	private void setTicketExpiresIn(int expiresIn){
		this.ticketExpiresIn = expiresIn;
		ticketTime = new Date();
	}

	/**
	 * 检查token时效性
	 * @return
	 */
	private boolean checkTokenExpireIn(){		
		long span = new Date().getTime() - tokenTime.getTime();
		LOG.info(
				"wxconfig-token时间:" + formatDatetime(tokenTime) 
				+ ",超时时间(expirein)(秒):" + tokenExpiresIn 
				+ ",已过时间(span)(秒):" + span/1000);
		if((span - tokenExpiresIn * 1000) > 0){
			return true;
		}
		return false;
	}

	/**
	 * 判断是Ticket否过期
	 * @return
	 */
	private boolean checkTicketExpireIn(){		
		long span = new Date().getTime() - ticketTime.getTime();
		LOG.info(
				"wxconfig-ticket时间:" + formatDatetime(ticketTime)
				+ ",超时时间(expirein)(秒):" + ticketExpiresIn 
				+ ",已过时间(span)(秒):" + span/1000);
		if((span - ticketExpiresIn * 1000) > 0){
			return true;
		}
		return false;
	}

	private static String formatDatetime(Date date){
		String r = "";
		try {
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			r = sdf.format(date);
		} catch (Exception e) {
			// TODO: handle exception
		}
		return r;
	}

	public String getTimestamp() {
		return timestamp;
	}

	public String getNonceStr() {
		return nonceStr;
	}
	
	public String getAccessToken() {
		checkTokenOrRefresh();
		return accessToken;
	}
	
	public String getAppId() {
		return appId;
	}
	
	public static class JsonTicket {

		@SerializedName("errcode")
		public int errcode;
		
		@SerializedName("errmsg")
		public String errmsg;
		
		@SerializedName("ticket")
		public String ticket;
		
		@SerializedName("expires_in")
		public int expiresIn;
	}
	
	public static class JsonToken {

		@SerializedName("access_token")
		public String accessToken;
		
		@SerializedName("expires_in")
		public int expiresIn;
		
		@Override
		public String toString() {
			return "token:" + accessToken + ",expiresIn:" + expiresIn;
		}
		
	}

}
